#include "stdafx.h"
#include "CSM.h"

#include "LoadShader.h"

#include "RenderingData.h"
#include "BoundingSphere.h"
#include "Frustum.h"
#include "Mesh.h"
#include "InputLayouts.h"
#include "MyMath.h"

#include "Renderer.h"

using namespace mgfx;

CSM::CSM()
{
	AllCascadesMap = Renderer::Instance->addDepthTexture(CASCADE_MAP_SIZE * 2, CASCADE_MAP_SIZE * 2, DXGI_FORMAT_D16_UNORM);
	mShaderShadowMap = loadShaderFromFile("ShadowMap.fx");
}

CSM::~CSM()
{
}

void CSM::renderDirectionalShadowMapInFrustum(const RenderingData & data, const mmath::FrustumPersp & frustum, uint cascadeIndex)
{
	// constructs rotation part of light view matrix
	XMVECTOR look = XMVector3Normalize(XMLoadFloat3(&data.MainLight.Direction)); // TODO: data.MainLight.Direction should be already normalized
	XMVECTOR right = XMVectorSet(1, 0, 0, 0); // watch out //XMLoadFloat3(&frustum.Right);
	XMVECTOR up = XMVector3Normalize(XMVector3Cross(look, right));
	right = XMVector3Cross(up, look);

	XMMATRIX lightViewRotation = mmath::matrixFromAxes(right, up, look);//XMMATRIX(right, up, look, XMVectorSet(0, 0, 0, 1));

	// transforms frustum vertices to light space and finds light space AABB of frustum
	// (actually it is light space only in the rotational sense. Position is still world space)
	//XMVECTOR minCoords = XMVectorReplicate( 999999999.0f);
	//XMVECTOR maxCoords = XMVectorReplicate(-999999999.0f);
	//for (uint i = 0; i < 8 ; i++)
	//{
	//	XMVECTOR lightSpacePt = XMVector3Transform(XMLoadFloat3(&frustum.Points[i]), lightViewRotation);
	//	minCoords = XMVectorMin(minCoords, lightSpacePt);
	//	maxCoords = XMVectorMax(maxCoords, lightSpacePt);
	//}

	// calculates center of frustum AABB in light space which will be used to find translation part of light view matrix
	//XMVECTOR center = XMVectorMultiply( XMVectorAdd(minCoords, maxCoords), XMVectorReplicate(0.5f) );

	// vector of cascade frustum bounds in all axes
	//XMVECTOR size = XMVectorAbs( XMVectorSubtract(maxCoords, minCoords) );

	// Previously, code above was used to determine bounds of frustum in light space
	// in order to generate projection matrix with correct width and height.
	// However, there was a problem with shimmering of shadow map while moving
	// and rotating camera, due to changes of projection width and height.
	// Translation-caused shimmering is fixed somewhere below,
	// and rotation-caused shimmering is fixed by using frustum's
	// bounding sphere as a base of projection matrix construction,
	// since bounding sphere doesn't change size when frustum rotates
	// (thus preventing projection from changing size)
	// as opposed to projection bounds computed like in the code above
	// (they change size since they are dependent on frustum's orientation
	// relative to light space).
	// Using bounding sphere instead of orientation-dependent bounds
	// causes non-optimal usage of shadow map.

	mmath::BoundingSphere frustumBSphere = mmath::getFrustumBoundingSphere(frustum.Points);
	XMVECTOR center = XMVector3Transform(XMLoadFloat3(&frustumBSphere.Center), lightViewRotation);

	// virtual position of light (it has to be far enough to "see" all shadow casters)
	XMVECTOR lightPos = XMVectorAdd(center, XMVectorSet(0, 0, -3000, 0));

	XMMATRIX lightView = lightViewRotation;
	lightView.r[3] = XMVectorSetW(XMVectorNegate(lightPos), 1);

	

	// calculates light projection matrix. Near plane distance is set as a small fraction of far plane distance. 
	// minCoords.z of frustum AABB can not be used, since there may be shadow casters in front of AABB.
	float projSize = frustumBSphere.Radius * 2;
	float zNear = 100, zFar = 4000;
	XMMATRIX lightProj = XMMatrixOrthographicLH(
		//XMVectorGetX(size), XMVectorGetY(size),
		projSize, projSize,
		zNear, zFar);

	XMMATRIX lightViewProj = XMMatrixMultiply(lightView, lightProj);

	// Following few lines serve correcting shadow shimmering effect when camera moves.
	// It is described here: http://www.gamedev.net/topic/591684-xna-40---shimmering-shadow-maps/
	// It forces the light projection to move only in "texel-sized increments"
	// Shimmering that is visible when camera rotates is fixed and described somewhere above.
	XMVECTOR testPoint = XMVector3Transform(XMVectorZero(), lightViewProj);
	testPoint *= XMVectorReplicate(CASCADE_MAP_SIZE * 0.5f);
	XMVECTOR testPointRounded = XMVectorRound(testPoint);
	XMVECTOR rounding = (testPointRounded - testPoint) / (CASCADE_MAP_SIZE * 0.5f);
	lightViewProj *= XMMatrixTranslation(XMVectorGetX(rounding), XMVectorGetY(rounding), 0.0f);
	// End of shadow shimmering correction code.

	// Store matrix for later use in object rendering shader
	XMStoreFloat4x4(&Matrices[cascadeIndex], lightViewProj);

	// Constructs light's orthogonal frustum for this cascade, to use in culling.
	XMVECTOR tmp;
	XMVECTOR worldSpaceLightPos = XMVector3Transform(XMVectorSet(0, 0, 0, 1), XMMatrixInverse(&tmp, lightView));
	mmath::FrustumOrtho lightFrustum;
	lightFrustum.construct(worldSpaceLightPos, look, up, right, projSize, projSize, zNear, zFar);

	Renderer * rdr = Renderer::Instance;

	rdr->setShader(mShaderShadowMap);

	uint meshesSize = data.Meshes.NumElements;
	for(uint meshIdx = 0; meshIdx < meshesSize; meshIdx++)
	{
		MeshInstance & meshInst = data.Meshes.Elements[meshIdx];
		Mesh & mesh = *meshInst.Mesh_;

		XMMATRIX worldViewProj = XMMatrixMultiply(XMLoadFloat4x4(&meshInst.WorldTransform), lightViewProj);

		rdr->setShaderMatrix("gWVP", (float*)&worldViewProj);

		uint partsSize = mesh.GetParts().size();
		for (uint partIdx = 0; partIdx < partsSize; partIdx++)
		{
			const Mesh::Part & part = mesh.GetParts()[partIdx];

			mmath::BoundingSphere meshBSphere = mmath::BoundingSphere::transform(part.BSphere,
				XMLoadFloat3(&meshInst.Position), XMLoadFloat4(&meshInst.Orientation), XMLoadFloat3(&meshInst.Scale));
			if(!mmath::frustumSphereintersects(lightFrustum.Planes, meshBSphere))
				continue;

			uint stride = sizeof(VertexPosition);
			uint offset = 0;

			rdr->setVertexBuffer(part.VBPositionsOnly);
			rdr->setIndexBuffer(part.IB);

			rdr->drawIndexed();
		}
	}

	uint bricksCount = data.Bricks.size();
	for(uint brickIdx = 0; brickIdx < bricksCount; brickIdx++)
	{
		const BrickGfxData & brick = data.Bricks[brickIdx];
		Mesh & mesh = *data.BrickMesh;

		XMMATRIX worldViewProj = XMMatrixMultiply(XMLoadFloat4x4(&brick.WorldMatrix), lightViewProj);

		rdr->setShaderMatrix("gWVP", (float*)&worldViewProj);

		uint partsSize = mesh.GetParts().size();
		for (uint partIdx = 0; partIdx < partsSize; partIdx++)
		{
			const Mesh::Part & part = mesh.GetParts()[partIdx];

			mmath::BoundingSphere meshBSphere = mmath::BoundingSphere(brick.Position, brick.BSphereRadius);
			if(!mmath::frustumSphereintersects(lightFrustum.Planes, meshBSphere))
				continue;

			uint stride = sizeof(VertexPosition);
			uint offset = 0;

			rdr->setVertexBuffer(part.VBPositionsOnly);
			rdr->setIndexBuffer(part.IB);

			rdr->drawIndexed();
		}
	}
}

void CSM::render(const RenderingData & data)
{
	const float SHADOW_SPLIT_FACTOR = 0.4f;

	const mmath::FrustumPersp & f = data.ViewFrustum;
	float depth = f.FarZ - f.NearZ;

	// Calc depths of fronts and ends of cascade frustums
	for (uint i = 0; i < NUM_CASCADES + 1 ; i++)
	{
		SplitDepths[i] = SHADOW_SPLIT_FACTOR * (f.NearZ + ((float)i / NUM_CASCADES) * (f.FarZ - f.NearZ)) +
			(1 - SHADOW_SPLIT_FACTOR) * (f.NearZ * powf(f.FarZ / f.NearZ, (float)i / NUM_CASCADES));
	}

	//mSplitDepths[0] = 5.0f;
	//mSplitDepths[1] = 500.0f;
	//mSplitDepths[2] = 1500.0f;
	//mSplitDepths[3] = 3000.0f;
	//mSplitDepths[4] = 6000.0f;
	// 

	Renderer * rdr = Renderer::Instance;
	Viewport oldVp = rdr->getViewport();

	Viewport vp;
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;
	vp.Width = (float)CASCADE_MAP_SIZE;
	vp.Height = (float)CASCADE_MAP_SIZE;

	rdr->setRenderTargets(0, NULL, AllCascadesMap);
	rdr->clear(false, NULL, true, 1.0f, false, 0);

	for (uint i = 0; i < NUM_CASCADES ; i++)
	{
		mmath::FrustumPersp cascadeFrustum;
		cascadeFrustum.construct(XMLoadFloat3(&f.Position), XMLoadFloat3(&f.Forward), XMLoadFloat3(&f.Up), XMLoadFloat3(&f.Right),
			f.FieldOfView, f.AspectRatio, SplitDepths[i], SplitDepths[i + 1]);

		// 4 cascades are stored in single texture
		vp.TopLeftX = i % 2 == 0 ? 0.0f : CASCADE_MAP_SIZE;
		vp.TopLeftY = i < 2		 ? 0.0f : CASCADE_MAP_SIZE;
		rdr->setViewport(vp);

		renderDirectionalShadowMapInFrustum(data, cascadeFrustum, i);
	}

	rdr->setViewport(oldVp);
}